#ifndef SCREA_CORE_RECT_H_
#define SCREA_CORE_RECT_H_

#include "ScreaTypes.h"
#include "Dimension.h"
#include "Vector2.h"

namespace screa { namespace core
{

    //! Rectangle template.
    /** Mostly used by 2D GUI elements and for 2D drawing methods.
    It has 2 positions instead of position and dimension and a fast
    method for collision detection with other rectangles and points.

    Coordinates are (0,0) for top-left corner, and increasing to the right
    and to the bottom.
    */
    template <class T>
    class rect
    {
    public:

        rect() : upperLeftCorner(0,0), lowerRightCorner(0,0) {}

        //! Constructor with two corners
        rect(T x, T y, T x2, T y2)
            : upperLeftCorner(x,y), lowerRightCorner(x2,y2) {}

        //! Constructor with two corners
        rect(const vector2d<T>& upperLeft, const vector2d<T>& lowerRight)
            : upperLeftCorner(upperLeft), lowerRightCorner(lowerRight) {}

        //! Constructor with upper left corner and dimension
        template <class U>
        rect(const vector2d<T>& pos, const dimension2d<U>& size)
            : upperLeftCorner(pos), lowerRightCorner(pos.x + size.width, pos.y + size.height) {}

        //! move right by given numbers
        rect<T> operator+(const vector2d<T>& pos) const
        {
            rect<T> ret(*this);
            return ret+=pos;
        }

        //! move right by given numbers
        rect<T>& operator+=(const vector2d<T>& pos)
        {
            upperLeftCorner += pos;
            lowerRightCorner += pos;
            return *this;
        }

        //! move left by given numbers
        rect<T> operator-(const vector2d<T>& pos) const
        {
            rect<T> ret(*this);
            return ret-=pos;
        }

        //! move left by given numbers
        rect<T>& operator-=(const vector2d<T>& pos)
        {
            upperLeftCorner -= pos;
            lowerRightCorner -= pos;
            return *this;
        }

        //! equality operator
        bool operator==(const rect<T>& other) const
        {
            return (upperLeftCorner == other.upperLeftCorner &&
                lowerRightCorner == other.lowerRightCorner);
        }

        //! inequality operator
        bool operator!=(const rect<T>& other) const
        {
            return (upperLeftCorner != other.upperLeftCorner ||
                lowerRightCorner != other.lowerRightCorner);
        }

        //! compares size of rectangles
        bool operator<(const rect<T>& other) const
        {
            return getArea() < other.getArea();
        }

        // methods

        //! Returns size of rectangle
        T getArea() const
        {
            return getWidth() * getHeight();
        }

        //! Returns if a 2d point is within this rectangle.
        /** \param pos Position to test if it lies within this rectangle.
        \return True if the position is within the rectangle, false if not. */
        bool isPointInside(const vector2d<T>& pos) const
        {
            return (upperLeftCorner.x <= pos.x &&
                upperLeftCorner.y <= pos.y &&
                lowerRightCorner.x >= pos.x &&
                lowerRightCorner.y >= pos.y);
        }

        //! Check if the rectangle collides with another rectangle.
        /** \param other Rectangle to test collision with
        \return True if the rectangles collide. */
        bool isRectCollided(const rect<T>& other) const
        {
            return (lowerRightCorner.y > other.upperLeftCorner.y &&
                upperLeftCorner.y < other.lowerRightCorner.y &&
                lowerRightCorner.x > other.upperLeftCorner.x &&
                upperLeftCorner.x < other.lowerRightCorner.x);
        }

        //! Clips this rectangle with another one.
        /** \param other Rectangle to clip with */
        void clipAgainst(const rect<T>& other)
        {
            if (other.lowerRightCorner.x < lowerRightCorner.x)
                lowerRightCorner.x = other.lowerRightCorner.x;
            if (other.lowerRightCorner.y < lowerRightCorner.y)
                lowerRightCorner.y = other.lowerRightCorner.y;

            if (other.upperLeftCorner.x > upperLeftCorner.x)
                upperLeftCorner.x = other.upperLeftCorner.x;
            if (other.upperLeftCorner.y > upperLeftCorner.y)
                upperLeftCorner.y = other.upperLeftCorner.y;

            // correct possible invalid rect resulting from clipping
            if (upperLeftCorner.y > lowerRightCorner.y)
                upperLeftCorner.y = lowerRightCorner.y;
            if (upperLeftCorner.x > lowerRightCorner.x)
                upperLeftCorner.x = lowerRightCorner.x;
        }

        //! Moves this rectangle to fit inside another one.
        /** \return True on success, false if not possible */
        bool constrainTo(const rect<T>& other)
        {
            if (other.getWidth() < getWidth() || other.getHeight() < getHeight())
                return false;

            T diff = other.lowerRightCorner.x - lowerRightCorner.x;
            if (diff < 0)
            {
                lowerRightCorner.x += diff;
                upperLeftCorner.x  += diff;
            }

            diff = other.lowerRightCorner.y - lowerRightCorner.y;
            if (diff < 0)
            {
                lowerRightCorner.y += diff;
                upperLeftCorner.y  += diff;
            }

            diff = upperLeftCorner.x - other.upperLeftCorner.x;
            if (diff < 0)
            {
                upperLeftCorner.x  -= diff;
                lowerRightCorner.x -= diff;
            }

            diff = upperLeftCorner.y - other.upperLeftCorner.y;
            if (diff < 0)
            {
                upperLeftCorner.y  -= diff;
                lowerRightCorner.y -= diff;
            }

            return true;
        }

        //! Get width of rectangle.
        T getWidth() const
        {
            return lowerRightCorner.x - upperLeftCorner.x;
        }

        //! Get height of rectangle.
        T getHeight() const
        {
            return lowerRightCorner.y - upperLeftCorner.y;
        }

        //! If the lower right corner of the rect is smaller then the upper left, the points are swapped.
        void repair()
        {
            if (lowerRightCorner.x < upperLeftCorner.x)
            {
                T t = lowerRightCorner.x;
                lowerRightCorner.x = upperLeftCorner.x;
                upperLeftCorner.x = t;
            }

            if (lowerRightCorner.y < upperLeftCorner.y)
            {
                T t = lowerRightCorner.y;
                lowerRightCorner.y = upperLeftCorner.y;
                upperLeftCorner.y = t;
            }
        }

        //! Returns if the rect is valid to draw.
        /** It would be invalid if the upperLeftCorner is lower or more
        right than the lowerRightCorner. */
        bool isValid() const
        {
            return ((lowerRightCorner.x >= upperLeftCorner.x) &&
                (lowerRightCorner.y >= upperLeftCorner.y));
        }

        //! Get the center of the rectangle
        vector2d<T> getCenter() const
        {
            return vector2d<T>(
                (upperLeftCorner.x + lowerRightCorner.x) / 2,
                (upperLeftCorner.y + lowerRightCorner.y) / 2);
        }

        //! Get the dimensions of the rectangle
        dimension2d<T> getSize() const
        {
            return dimension2d<T>(getWidth(), getHeight());
        }


        //! Adds a point to the rectangle
        /** Causes the rectangle to grow bigger if point is outside of
        the box
        \param p Point to add to the box. */
        void addInternalPoint(const vector2d<T>& p)
        {
            addInternalPoint(p.x, p.y);
        }

        //! Adds a point to the bounding rectangle
        /** Causes the rectangle to grow bigger if point is outside of
        the box
        \param x x-Coordinate of the point to add to this box.
        \param y y-Coordinate of the point to add to this box. */
        void addInternalPoint(T x, T y)
        {
            if (x>lowerRightCorner.x)
                lowerRightCorner.x = x;
            if (y>lowerRightCorner.y)
                lowerRightCorner.y = y;

            if (x<upperLeftCorner.x)
                upperLeftCorner.x = x;
            if (y<upperLeftCorner.y)
                upperLeftCorner.y = y;
        }

        //! Upper left corner
        vector2d<T> upperLeftCorner;
        //! Lower right corner
        vector2d<T> lowerRightCorner;
    };

    typedef rect<f32> rectf;
    typedef rect<i32> recti;

} }

#endif

